package com.fa4j.common.data.repository.impl;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.repository.CrudRepository;
import com.fa4j.common.base.model.Page;
import com.fa4j.common.base.model.PageQuery;
import com.fa4j.common.data.annotation.Query;
import com.fa4j.common.data.model.QueryType;
import com.fa4j.common.data.repository.BaseRepository;
import com.google.common.base.CaseFormat;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

@Slf4j
public class BaseRepositoryImpl<M extends BaseMapper<T>, T> extends CrudRepository<M, T> implements BaseRepository<T> {

    @Override
    public <Q extends PageQuery> Page<T> queryPage(Q query) {
        QueryChainWrapper<T> queryWrapper = super.query();
        if (!ObjectUtils.isEmpty(query.getColumns())) {
            queryWrapper.select(query.getColumns());
        } else {
            queryWrapper.select(this.getEntityClass(), p -> !Collection.class.isAssignableFrom(p.getPropertyType()));
        }
        queryWrapper.orderBy(!ObjectUtils.isEmpty(query.getOrderBy()), Boolean.TRUE.equals(query.getOrderAsc()),
                query.getOrderBy());
        ReflectionUtils.doWithLocalFields(query.getClass(), f -> {
            f.setAccessible(true);
            Object fieldValue = f.get(query);
            String fieldName = f.getName();
            QueryType queryType = QueryType.EQ;
            boolean isNull = false;
            Query queryAnn = f.getAnnotation(Query.class);
            if (queryAnn != null) {
                if (!ObjectUtils.isEmpty(queryAnn.field())) {
                    fieldName = queryAnn.field();
                }
                isNull = queryAnn.isNull();
                queryType = queryAnn.type();
            }
            fieldName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, fieldName);
            if (!ObjectUtils.isEmpty(fieldValue)) {
                switch (queryType) {
                    case EQ -> queryWrapper.eq(fieldName, fieldValue);
                    case LIKE -> queryWrapper.like(fieldName, fieldValue);
                    case GE -> queryWrapper.ge(fieldName, fieldValue);
                    case GT -> queryWrapper.gt(fieldName, fieldValue);
                    case IN -> queryWrapper.in(fieldName, fieldValue);
                    case LE -> queryWrapper.le(fieldName, fieldValue);
                    case LT -> queryWrapper.lt(fieldName, fieldValue);
                }
            } else if (isNull) {
                queryWrapper.isNull(fieldName);
            }
        });
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<T> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(query.getCurrent(), query.getSize());
        if (!ObjectUtils.isEmpty(query.getOrderBy())) {
            OrderItem orderItem = new OrderItem();
            orderItem.setAsc(Boolean.TRUE.equals(query.getOrderAsc()));
            orderItem.setColumn(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, query.getOrderBy()));
            page.setOrders(List.of(orderItem));
        }
        page.setSearchCount(query.getSearchCount());
        IPage<T> p = queryWrapper.page(page);
        return Page.<T>builder()
                .current(p.getCurrent())
                .size(p.getSize())
                .total(p.getTotal())
                .records(p.getRecords())
                .orderBy(query.getOrderBy())
                .orderAsc(query.getOrderAsc())
                .build();
    }

    @Override
    public List<T> queryByIds(Collection<? extends Serializable> ids) {
        if (ObjectUtils.isEmpty(ids)) {
            return List.of();
        }
        return super.listByIds(ids);
    }

    @Override
    public Optional<T> findById(Serializable id) {
        return super.getOptById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean create(T entity) {
        return super.save(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modify(T entity) {
        return super.updateById(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean remove(T entity) {
        return super.removeById(entity);
    }

}
